﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using LINQPad.Extensibility.DataContext;
using LINQPad.OpenAccess.Helpers;
using LINQPad.OpenAccess.Metadata.Reflection;

namespace LINQPad.OpenAccess.Metadata
{
    /// <summary>
    /// Used to parse the open access context assemblies.
    /// </summary>
    public partial class AssemblyParser : IAssemblyParser
    {
        #region Private

        #region - Vars

        private readonly IContextTypeLocator[] _contextTypeLocators;
        private readonly IMetadataSourceLocator[] _metadataSourceLocators;

        #endregion

        #endregion

        #region Public

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyParser" /> class.
        /// </summary>
        /// <param name="contextTypeLocators">The context type locators.</param>
        public AssemblyParser( IEnumerable<IContextTypeLocator> contextTypeLocators, IEnumerable<IMetadataSourceLocator> metadataSourceLocators )
        {
            Guard.ArgumentNotNull( contextTypeLocators, "contextTypeLocators" );
            Guard.ArgumentNotNull( metadataSourceLocators, "metadataSourceLocators" );

            this._contextTypeLocators = contextTypeLocators.ToArray( );
            this._metadataSourceLocators = metadataSourceLocators.ToArray( );
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets the context type locators.
        /// </summary>
        /// <value>
        /// The context type locators.
        /// </value>
        public IContextTypeLocator[] ContextTypeLocators
        {
            get
            {
                return this._contextTypeLocators;
            }
        }

        /// <summary>
        /// Gets the metadata source locators.
        /// </summary>
        /// <value>
        /// The metadata source locators.
        /// </value>
        public IMetadataSourceLocator[] MetadataSourceLocators
        {
            get
            {
                return this._metadataSourceLocators;
            }
        }

        #endregion

        #endregion

        #region IAssemblyParser Members

        /// <summary>
        /// Discovers the context types.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>
        /// The context types.
        /// </returns>
        public IEnumerable<IContextTypeInfo> DiscoverContextTypes( Assembly assembly )
        {
            Guard.ArgumentNotNull( assembly, "assembly" );

            return this._contextTypeLocators
                       .SelectMany( c => c.GetFromAssembly( assembly ) );
        }

        /// <summary>
        /// Discovers the metadata sources.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>
        /// The metadata sources.
        /// </returns>
        public IEnumerable<IMetadataSourceInfoWithSource> DiscoverMetadataSources( Assembly assembly )
        {
            Guard.ArgumentNotNull( assembly, "assembly" );

            return this._metadataSourceLocators
                       .SelectMany( c => c.GetFromAssembly( assembly ) );
        }

        /// <summary>
        /// Parses the specified assembly.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <returns>The parse result.</returns>
        /// <exception cref="System.ArgumentNullException">assembly</exception>
        public IAssemblyParseResult Parse( Assembly assembly )
        {
            Guard.ArgumentNotNull( assembly, "assembly" );

            var contextTypes = this.DiscoverContextTypes( assembly ).ToArray( );
            if ( contextTypes.Length == 0 )
                return AssemblyParseResult.Empty;

            var metadataSources = this.DiscoverMetadataSources( assembly ).ToArray( );
            return new AssemblyParseResult( contextTypes, metadataSources );
        }
        /// <summary>
        /// Parses the specified assembly file.
        /// </summary>
        /// <param name="assemblyFile">The assembly file.</param>
        /// <returns>
        /// The parse result.
        /// </returns>
        /// <exception cref="System.IO.FileNotFoundException">If no such assembly exists.</exception>
        public IAssemblyParseResult Parse( string assemblyFile )
        {
            Guard.ArgumentNotNullOrEmpty( assemblyFile, "assemblyFile" );
            if ( !File.Exists( assemblyFile ) )
                throw new FileNotFoundException( "No such assembly '{0}' !", assemblyFile );

            var assembly = DataContextDriver.LoadAssemblySafely( assemblyFile );
            return this.Parse( assembly );
        }

        #endregion
    }
}